package dragon.social.core.rewrite;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.Charset;

/**
 * 由于spring 自带的OAuth2Template有不足的地方，因此要重写此类
 */
public class TextOAuth2Template extends OAuth2Template {
    /**
     * 自定义一个参数，用于在解析服务提供商返回token时，是用json的方式解释，还是txt的方法（QQ要用txt）
     */
    private int tokenResponseType = 0;

    /**
     * 默认值，表示服务提供商返回的token信息是json格式
     */
    public static final int TOKEN_RESPONSE_TYPE_JSON = 0;
    /**
     * QQ返回的token信息是字符串：access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
     */
    public static final int TOKEN_RESPONSE_TYPE_QQ = 1;

    /**
     * Constructs an OAuth2Template for a given set of client credentials.
     * Assumes that the authorization URL is the same as the authentication URL.
     *
     * @param clientId       the client ID
     * @param clientSecret   the client secret
     * @param authorizeUrl   the base URL to redirect to when doing authorization code or implicit grant authorization
     * @param accessTokenUrl the URL at which an authorization code, refresh token, or user credentials may be exchanged for an access token.
     */
    public TextOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl, int tokenResponseType) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        this.tokenResponseType = tokenResponseType;
        //在父类的请求token方法exchangeForAccess，需要UseParametersForClientAuthentication为true，才会发client id和client secret去服务提供商，因此这里把其设为true
        setUseParametersForClientAuthentication(true);
    }


    /**
     * Constructs an OAuth2Template for a given set of client credentials.
     * Assumes that the authorization URL is the same as the authentication URL.
     *
     * @param clientId       the client ID
     * @param clientSecret   the client secret
     * @param authorizeUrl   the base URL to redirect to when doing authorization code or implicit grant authorization
     * @param accessTokenUrl the URL at which an authorization code, refresh token, or user credentials may be exchanged for an access token.
     */
    public TextOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        this(clientId, clientSecret, authorizeUrl, accessTokenUrl,TOKEN_RESPONSE_TYPE_JSON);
    }

    /**
     * Constructs an OAuth2Template for a given set of client credentials.
     *
     * @param clientId        the client ID
     * @param clientSecret    the client secret
     * @param authorizeUrl    the base URL to redirect to when doing authorization code or implicit grant authorization
     * @param authenticateUrl the URL to redirect to when doing authentication via authorization code grant
     * @param accessTokenUrl  the URL at which an authorization code, refresh token, or user credentials may be exchanged for an access token
     */
    public TextOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String authenticateUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, authenticateUrl, accessTokenUrl);
        this.tokenResponseType = TOKEN_RESPONSE_TYPE_JSON;
        setUseParametersForClientAuthentication(true);
    }

    /**
     * Creates the {@link RestTemplate} used to communicate with the provider's OAuth 2 API.
     * This implementation creates a RestTemplate with a minimal set of HTTP message converters ({@link FormHttpMessageConverter} and {@link MappingJackson2HttpMessageConverter}).
     * May be overridden to customize how the RestTemplate is created.
     * For example, if the provider returns data in some format other than JSON for form-encoded, you might override to register an appropriate message converter.
     *
     * @return a {@link RestTemplate} used to communicate with the provider's OAuth 2 API
     */
    @Override
    protected RestTemplate createRestTemplate() {
        RestTemplate restTemplate = super.createRestTemplate();
        /*父类的RestTemplate，在创建时并没有添加对Text/Html的数据返回支持。只支持两种表格和JSON。
        而QQ是返回Text/Html类型的数据。因此要在这里重写此方法，为其添加Text/Html的converter
         */
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }

    /**
     * Posts the request for an access grant to the provider.
     * The default implementation uses RestTemplate to request the access token and expects a JSON response to be bound to a Map. The information in the Map will be used to create an {@link AccessGrant}.
     * Since the OAuth 2 specification indicates that an access token response should be in JSON format, there's often no need to override this method.
     * If all you need to do is capture provider-specific data in the response, you should override createAccessGrant() instead.
     * However, in the event of a provider whose access token response is non-JSON, you may need to override this method to request that the response be bound to something other than a Map.
     * For example, if the access token response is given as form-encoded, this method should be overridden to call RestTemplate.postForObject() asking for the response to be bound to a MultiValueMap (whose contents can then be used to create an AccessGrant).
     *
     * @param accessTokenUrl the URL of the provider's access token endpoint.
     * @param parameters     the parameters to post to the access token endpoint.
     * @return the access grant.
     */
    @Override
    protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
        /*
        在spring social里面，在获取了服务提供商返回的信息后，默认是当做json处理，从中提取相关的信息。当做json处理的话，在MessageConverter里面，就可以转成map，从而通过key获得value。如下图：
        然而~ QQ返回的信息，并不是json，而是字符串，格式如下：
        access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
        参考QQ Api  https://wiki.connect.qq.com/%E4%BD%BF%E7%94%A8authorization_code%E8%8E%B7%E5%8F%96access_token
        所以，需要重写PostForAccessGrant的方法，让其处理QQ的内容。
        为了能让此template可以通用，所以加了一个属性去判断走向
         */
        if (this.tokenResponseType == 1) {
            String response = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
            /*
            split()方法： 分割字符串过程中会自动忽略所有的空项；
            splitByWholeSeparator方法：分割字符串过程中会忽略中间的空项，保留末尾的空项；
            splitByWholeSeparatorPreserveAllToKens方法及splitPreserveAllToKens方法作用相同：分割字符串过程中会按照每个分隔符进行分割，不忽略任何空白项；
            splitByCharacterType方法及splitByCharacterTypeCamelCase方法：按照字符类型进行分割。
             */
            String[] allTokens = StringUtils.splitByWholeSeparatorPreserveAllTokens(response, "&");
            String accessToken = StringUtils.substringAfter(allTokens[0], "=");
            Long expiresIn = Long.valueOf(StringUtils.substringAfter(allTokens[1], "="));
            String refreshToken = StringUtils.substringAfter(allTokens[2], "=");
            return new AccessGrant(accessToken,null,refreshToken,expiresIn);
        } else {
            return super.postForAccessGrant(accessTokenUrl, parameters);
        }
    }
}
